<?php
include_once 'config.php';

class OPDS {
	protected $lib;
	protected $index;
	protected $link_base;
	public $idx;

	function __construct($lib, $index, $link_base) {
		$this->lib = $lib;
		$this->index = $index;
		$this->link_base = $link_base;
	}

	function header() {
		header('Content-Type: application/atom+xml;charset=utf-8');
		$link_base = $this->link_base;
		echo <<<EOT
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<link rel="self"  
		href="$link_base/index.php" 
		type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
	<link rel="start" 
		href="$link_base/index.php" 
		type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
	<title>El Fuego OPDS server</title>
	<updated>2010-01-10T10:03:10Z</updated>
	<author>
		<name>El Fuego</name>
		<uri>http://elfuego.biz</uri>
	</author>
EOT;
	}

	function footer() {
		echo "\n</feed>";
	}

	function section($name, $link, $descr) {
		$this->section1('subsection', $name, $link, $descr);
	}

	function section1($rel, $name, $link, $descr) {
		$link = $this->link_base . '/' . $link;
		$uid = md5($link);
		echo <<<EOT
	<entry>
		<id>tag:sec:$uid</id>
		<title>$name</title>
		<link rel="$rel" 
			href="$link"
			type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
		<updated>2010-01-10T10:01:01Z</updated>
		<content type="text">$descr</content>
	</entry>
EOT;
	}

	function entry($book) {
		$id = $book['id'];
		$title = $book['title'];
		$link_base = $this->link_base;
		echo <<<EOT

	<entry>
		<title>$title</title>
		<id>tag:book:$id</id>
EOT;
		$this->index_book_authors($book['id'], function ($row) use ($link_base) {
			global $opds;
			$auth_name = $opds->get_auth_name($row);
			$auth_id = $row['id'];
			echo <<<EOT

		<author>
			<name>$auth_name</name>
			<uri>$link_base/books.php?a=$auth_id</uri>
		</author>
EOT;
		});
		$updated = date('Y-m-d', $book['date']);
		$lang_id = $book['lang'];
		if ($lang_id) {
			$lang = $this->index_get_lang_by_id($lang_id);
			echo "\n		<dc:language>$lang</dc:language>\n";
		}
		echo <<<EOT
		<updated>${updated}T00:00:00Z</updated>
		<link href="$link_base/book.php?i=$id" rel="http://opds-spec.org/acquisition/open-access" type="application/fb2+zip"/>
	</entry>
EOT;
	}

	function get_book_data($id) {
		$book = $this->index_get_book_by_id($id);
		$za = new ZipArchive();
		$za->open($this->lib . '/' . $book['archive']);
		$name = $book['file'] . '.' . $book['ext'];
		$content = $za->getFromName($name);
		if ($content === false)
			return;
		require_once('zip.php');
		$zip = new zipfile();
		$zip->addFile($content, $name);
		$name .= '.zip';
		header("Content-type: application/x-fictionbook");
		header("Content-Disposition: attachment; filename=$name");
		header("Content-Description: Files of an applicant");
		echo $zip->file();
	}

	function search_for($needle) {
		$needle = mb_strtoupper($needle);
		$this->scan_inpx(function ($af) use ($needle) {
			if ($this->has_substr(mb_strtoupper($af[0]), $needle) || $this->has_substr(mb_strtoupper($af[2]), $needle))
				print_r($af);
		});
	}

	function get_genres() {
		$genres = array();
		$this->scan_inpx(function ($af) use (&$genres) {
			$agenres = explode(":", $af[1]);
			foreach ($agenres as $genre) {
				if (empty($genre))
					continue;
				if (!in_array($genre, $genres))
					array_push($genres, $genre);
			}
		});
		sort($genres);
		return $genres;
	}

	function scan_inpx($func) {
		$za = new ZipArchive();
		$za->open($this->index);

		for ($i=0; $i < $za->numFiles; $i++) {
			$info = $za->statIndex($i);
			$needle = ".inp";
			if (substr($info['name'], -strlen($needle)) !== $needle)
				continue;
			$archname = substr($info['name'], 0, -strlen($needle)) . '.zip';
			$ipack = $za->getFromIndex($i);
			$apack = explode("\n", $ipack);
			foreach ($apack as $ifile) {
				if (empty($ifile))
					continue;
				$afile = explode("\x04", $ifile);
				$func($afile, $archname);
			}
		}
	}

	function has_substr($haystack, $needle) {
		return mb_strpos($haystack, $needle) !== false;
	}

	function starts_with($haystack, $needle) {
		return mb_strpos($haystack, $needle) === 0;
	}

	function open_index() {
		$this->idx = new PDO('sqlite:books.sqlite3');
		$this->idx->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	}

	function index_create_structure() {
		$this->idx->exec("DROP TABLE IF EXISTS book_genres");
		$this->idx->exec("DROP TABLE IF EXISTS book_authors");
		$this->idx->exec("DROP TABLE IF EXISTS langs");
		$this->idx->exec("DROP TABLE IF EXISTS genres");
		$this->idx->exec("DROP TABLE IF EXISTS authors");
		$this->idx->exec("DROP TABLE IF EXISTS books");
		$this->idx->exec("CREATE TABLE langs (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    title TEXT)");
		$this->idx->exec("CREATE TABLE genres (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    title TEXT)");
		$this->idx->exec("CREATE TABLE authors (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    lastname TEXT,
                    firstname TEXT,
                    middlename TEXT)");
		$this->idx->exec("CREATE TABLE books (
                    id INTEGER PRIMARY KEY AUTOINCREMENT, 
                    title TEXT,
                    archive TEXT,
                    file TEXT,
                    ext TEXT,
                    size INTEGER,
                    lang INTEGER,
                    date INTEGER,
                    librate INTEGER)");
		$this->idx->exec("CREATE TABLE book_genres (
                    book_id INTEGER,
                    genre_id INTEGER)");
		$this->idx->exec("CREATE TABLE book_authors (
                    book_id INTEGER,
                    author_id INTEGER)");
	}

	function index_create_indices() {
		$this->idx->exec("CREATE UNIQUE INDEX langs_title_idx ON langs(title)");
		$this->idx->exec("CREATE UNIQUE INDEX genres_title_idx ON genres(title)");
		$this->idx->exec("CREATE UNIQUE INDEX authors_idx ON authors(lastname, firstname, middlename)");
		$this->idx->exec("CREATE INDEX authors_lastname_idx ON authors(lastname)");
		$this->idx->exec("CREATE INDEX books_title_idx ON books(title)");
		$this->idx->exec("CREATE INDEX book_genres_book_idx ON book_genres(book_id)");
		$this->idx->exec("CREATE INDEX book_genres_genre_idx ON book_genres(genre_id)");
		$this->idx->exec("CREATE INDEX book_authors_book_idx ON book_authors(book_id)");
		$this->idx->exec("CREATE INDEX book_authors_author_idx ON book_authors(author_id)");
	}

	function index_add_book($book) {
		$stmt = $this->idx->prepare(
			"INSERT INTO books (title, archive, file, ext, size, lang, date, librate)
					VALUES (:title, :archive, :file, :ext, :size, :lang, :date, :librate)");
		$stmt->bindValue(':title', $book['title'], PDO::PARAM_STR);
		$stmt->bindValue(':archive', $book['archive'], PDO::PARAM_STR);
		$stmt->bindValue(':file', $book['file'], PDO::PARAM_STR);
		$stmt->bindValue(':ext', $book['ext'], PDO::PARAM_STR);
		$stmt->bindValue(':size', $book['size'], PDO::PARAM_INT);
		$stmt->bindValue(':lang', $book['lang'], PDO::PARAM_INT);
		$stmt->bindValue(':date', $book['date'], PDO::PARAM_INT);
		$stmt->bindValue(':librate', $book['librate'], PDO::PARAM_INT);
		$stmt->execute();
		return $this->index_lastid();
	}

	function index_get_lang($title) {
		$result = $this->idx->query('SELECT * FROM langs WHERE title="' . $title . '"');
		return $result->fetch();
	}

	function index_get_lang_by_id($id) {
		$result = $this->idx->query('SELECT title FROM langs WHERE id=' . $id)->fetch();
		return $result[0];
	}

	function index_add_lang($title) {
		$stmt = $this->idx->prepare("INSERT INTO langs (title) VALUES (:title)");
		$stmt->bindValue(':title', $title, PDO::PARAM_STR);
		$stmt->execute();
		return $this->index_lastid();
	}

	function index_get_genre($title) {
		$result = $this->idx->query('SELECT * FROM genres WHERE title="' . $title . '"');
		return $result->fetch();
	}

	function index_add_genre($title) {
		$stmt = $this->idx->prepare("INSERT INTO genres (title) VALUES (:title)");
		$stmt->bindValue(':title', $title, PDO::PARAM_STR);
		$stmt->execute();
		return $this->index_lastid();
	}

	function index_get_author($lastname, $firstname, $middlename) {
		$query = $this->idx->prepare('SELECT * FROM authors WHERE UPPER(lastname)=:lastname and 
					UPPER(firstname)=:firstname and UPPER(middlename)=:middlename');
		$query->bindValue(':lastname', mb_strtoupper($lastname), PDO::PARAM_STR);
		$query->bindValue(':firstname', mb_strtoupper($firstname), PDO::PARAM_STR);
		$query->bindValue(':middlename', mb_strtoupper($middlename), PDO::PARAM_STR);
		$query->execute();
		return $query->fetch();
	}

	function index_get_book_by_id($id) {
		$query = $this->idx->prepare('SELECT * FROM books WHERE id=:id');
		$query->bindValue(':id', $id, PDO::PARAM_INT);
		$query->execute();
		return $query->fetch();
	}

	function index_add_author($lastname, $firstname, $middlename) {
		$stmt = $this->idx->prepare("INSERT INTO authors (lastname, firstname, middlename) 
				VALUES (:lastname, :firstname, :middlename)");
		$stmt->bindValue(':lastname', $lastname, PDO::PARAM_STR);
		$stmt->bindValue(':firstname', $firstname, PDO::PARAM_STR);
		$stmt->bindValue(':middlename', $middlename, PDO::PARAM_STR);
		$stmt->execute();
		return $this->index_lastid();
	}

	function index_add_book_genre($book_id, $genre_id) {
		$stmt = $this->idx->prepare("INSERT INTO book_genres(book_id, genre_id) VALUES (:book_id, :genre_id)");
		$stmt->bindValue(':book_id', $book_id, PDO::PARAM_INT);
		$stmt->bindValue(':genre_id', $genre_id, PDO::PARAM_INT);
		$stmt->execute();
		return $this->index_lastid();
	}

	function index_add_book_author($book_id, $author_id) {
		$stmt = $this->idx->prepare("INSERT INTO book_authors(book_id, author_id) VALUES (:book_id, :author_id)");
		$stmt->bindValue(':book_id', $book_id, PDO::PARAM_INT);
		$stmt->bindValue(':author_id', $author_id, PDO::PARAM_INT);
		$stmt->execute();
		return $this->index_lastid();
	}

	function index_lastid() {
		$result = $this->idx->query("SELECT last_insert_rowid()")->fetch();
		return $result[0];
	}

	function index_begin() {
		$this->idx->beginTransaction();
	}

	function index_commit() {
		$this->idx->commit();
	}

	function index_find_authors($q, $func) {
		$q = mb_strtoupper($this->escape_text($q));
		$ql = mb_strlen($q);
		if ($ql > 0) {
			$query = $this->idx->prepare('SELECT * FROM authors WHERE UPPER(lastname)=:q');
			$query->bindValue(':q', $q, PDO::PARAM_STR);
			$query->execute();
			foreach($query as $row)
				$func($row, true);
		}
		$is_full = false;
		if ($ql >= 4) {
			$query = $this->idx->prepare('SELECT * FROM authors WHERE UPPER(lastname) LIKE :q');
			$query->bindValue(':q', $q . '%', PDO::PARAM_STR);
			$is_full = true;
		} else if ($ql > 0) {
			$query = $this->idx->prepare('SELECT DISTINCT SUBSTR(lastname, 1, :ql) AS z FROM authors WHERE UPPER(lastname) LIKE :q ORDER BY z');
			$query->bindValue(':q', $q . '%', PDO::PARAM_STR);
			$query->bindValue(':ql', $ql + 1, PDO::PARAM_INT);
		} else
			$query = $this->idx->prepare('SELECT DISTINCT SUBSTR(lastname, 1, 1) AS z FROM authors ORDER BY z');
		$query->execute();
		foreach($query as $row) {
			if (!$is_full) {
				$rr = trim($row[0]);
				if (empty($rr) || mb_strtoupper($rr) === $q)
					continue;
			}
			$func($row, $is_full);
		}
	}

	function index_find_books_of($a, $func) {
		$query = $this->idx->prepare('SELECT b.* FROM books b join book_authors ba ON b.id=ba.book_id WHERE ba.author_id=:a');
		$query->bindValue(':a', $a, PDO::PARAM_INT);
		$query->execute();
		foreach($query as $row)
			$func($row);
	}

	function index_book_authors($b, $func) {
		$query = $this->idx->prepare('SELECT a.* FROM authors a join book_authors ba ON a.id=ba.author_id WHERE ba.book_id=:b');
		$query->bindValue(':b', $b, PDO::PARAM_INT);
		$query->execute();
		foreach($query as $row)
			$func($row);
	}

	function escape_text($text) {
		$quotes = array("\x27", "\x22", "\x60", "\t", "\n", "\r", "*", "%", "<", ">", "?", "!");
		$goodquotes = array("-", "+", "#");
		$repquotes = array("\-", "\+", "\#");
		$text = trim(strip_tags($text));
		$text = str_replace($quotes, '', $text);
		$text = str_replace($goodquotes, $repquotes, $text);
		$text = preg_replace("/ +/", " ", $text);
		return $text;
	}

	function get_auth_name($author) {
		return $author['lastname'] . ' ' . $author['firstname'] . ' ' . $author['middlename'];
	}
}
